﻿#include "pch.h"
#include "sbcs.h"
#include "extend/sbmem.h"
#include "kstub_mutex.h"

#if _SB_WINDOWS_ && !defined(_HEAP_MAXREQ)
#ifdef  _WIN64
#define _HEAP_MAXREQ    0xFFFFFFFFFFFFFFE0
#else
#define _HEAP_MAXREQ    0xFFFFFFE0
#endif
#endif

//////////////////////////////////////////////////////////////////////////
// 힙
static struct kImplHeap
{
#if _SB_WINDOWS_
	HANDLE              heap;
#endif

	ksize_t				size;
	ksize_t				max;
	ksize_t				cnt_alloc;
	ksize_t				cnt_free;
} k_impl_heap =
{
#if _SB_WINDOWS_
	NULL,
#endif
	0, 0, 0, 0
};

// 초기화
void _k_heap_init(void)
{
#if _SB_WINDOWS_
#if _SB_WINDOWS_DESKTOP_
	k_impl_heap.heap = HeapCreate(0/*|HEAP_NO_SERIALIZE*/, KMEM_PARA_SIZE, 0);
#else
	k_impl_heap.heap = GetProcessHeap();
#endif

	if (!k_impl_heap.heap)
	{
		k_traces(KLV_CRITICAL, "k.heap", "cannot create HEAP");
		k_exit(255);
	}
#endif

#if _SB_DEBUG_ && _SB_WINDOWS_DESKTOP_
	K_STMT_BEGIN{
		ULONG hfv = 2;
		if (HeapSetInformation(k_impl_heap.heap, HeapCompatibilityInformation, &hfv, sizeof(ULONG)))
			k_traces(KLV_RESULT, "k.heap", "LFH enabled");
	} K_STMT_END;
#endif
}

// 끝냄
void _k_heap_disp(void)
{
#if _SB_WINDOWS_DESKTOP_
	if (k_impl_heap.heap)
	{
#if _SB_DEBUG_
		SIZE_T st;

		if (HeapQueryInformation(k_impl_heap.heap, /*HeapEnableTerminationOnCorruption*/(HEAP_INFORMATION_CLASS)1, NULL, 0, &st))
			k_tracef(KLV_RESULT, "hb.heap", "unsafe HEAP count: %d", (int)st);
#endif
		HeapDestroy(k_impl_heap.heap);
		k_impl_heap.heap = NULL;
	}
#endif
}

#if _SB_WINDOWS_
// 힙 할당
static kpointer _k_heap_win32_alloc(ksize_t size, kuint flags)
{
	kpointer ptr;

#if _MSC_VER && _SB_DEBUG_
	__try
	{
		ptr = HeapAlloc(k_impl_heap.heap, (DWORD)flags, size);
	}
	__except (_exception_code() == STATUS_NO_MEMORY ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
	{
		if (GetExceptionCode() == STATUS_NO_MEMORY)
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);

		ptr = NULL;
	}
#else
	ptr = HeapAlloc(k_impl_heap.heap, (DWORD)flags, size);
#endif

	return ptr;
}

// 재할당
static kpointer _k_heap_win32_realloc(kpointer oldptr, ksize_t size, kuint flags)
{
	kpointer ptr;

#if _MSC_VER && _SB_DEBUG_
	__try
	{
		ptr = HeapReAlloc(k_impl_heap.heap, (DWORD)flags, oldptr, size);
	}
	__except (_exception_code() == STATUS_NO_MEMORY ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
	{
		if (GetExceptionCode() == STATUS_NO_MEMORY)
			SetLastError(ERROR_NOT_ENOUGH_MEMORY);

		ptr = NULL;
	}
#else
	ptr = HeapReAlloc(k_impl_heap.heap, (DWORD)flags, oldptr, size);
#endif

	return ptr;
}

// 힘 프리
static void _k_heap_win32_free(kpointer ptr)
{
	if (!HeapFree(k_impl_heap.heap, 0, ptr))
		errno = EINVAL;
}

// 힙 맞나?
static kcham _k_heap_win32_valid(kpointer ptr)
{
#if _SB_WINDOWS_DESKTOP_
	return HeapValidate(k_impl_heap.heap, 0, ptr) != 0;
#else
	return TRUE;
#endif
}
#endif

/**
 * 힙 크기.
 * @return	전체 힙 크기.
 */
KAPI ksize_t k_heapsize(void)
{
	return k_impl_heap.size;
}

/**
 * 힙 최대 크기.
 * @return	최대로 할당되었던 힙 크기.
 */
KAPI ksize_t k_heapmax(void)
{
	return k_impl_heap.max;
}

/**
 * 힙 포인터.
 * @return	힙 포인터.
 */
KAPI kpointer k_getheap(void)
{
#if _SB_WINDOWS_
	return (kpointer)k_impl_heap.heap;
#else
	return NULL;
#endif
}

// 힙 할당.
KAPI kpointer k_heapalloc(ksize_t size)
{
	kpointer ptr;

#if _SB_WINDOWS_
	if (size <= _HEAP_MAXREQ)
	{
		for (;;)
		{
			ptr = _k_heap_win32_alloc(size, HEAP_ZERO_MEMORY);

			if (ptr)
			{
				k_atominc_ptr(&k_impl_heap.cnt_alloc);
				break;
			}
			else
			{
				errno = ENOMEM;
				break;
			}
		}
	}
	else
	{
		errno = ENOMEM;
		ptr = NULL;
	}
#else
	ptr = calloc(size, sizeof(kbyte));

	if (ptr)
		k_atominc_ptr(&k_impl_heap.cnt_alloc);
#endif

	return ptr;
}

// 힙 할당.
KAPI kpointer k_heapallocf(ksize_t size)
{
	kpointer ptr;

#if _SB_WINDOWS_
	if (size <= _HEAP_MAXREQ)
	{
		for (;;)
		{
			ptr = _k_heap_win32_alloc(size, 0);

			if (ptr)
			{
				k_atominc_ptr(&k_impl_heap.cnt_alloc);
				break;
			}
			else
			{
				errno = ENOMEM;
				break;
			}
		}
	}
	else
	{
		errno = ENOMEM;
		ptr = NULL;
	}
#else
	ptr = malloc(size);

	if (ptr)
		k_atominc_ptr(&k_impl_heap.cnt_alloc);
#endif

	return ptr;
}

// 힙 재할당.
KAPI kpointer k_heaprealloc(kpointer oldptr, ksize_t newsize)
{
	kpointer ptr;

#if _SB_WINDOWS_
	if (newsize <= _HEAP_MAXREQ)
	{
		for (;;)
		{
			ptr = _k_heap_win32_realloc(oldptr, newsize, 0);

			if (ptr)
			{
				k_atominc_ptr(&k_impl_heap.cnt_alloc);
				k_atominc_ptr(&k_impl_heap.cnt_free);
				break;
			}
			else
			{
				errno = ENOMEM;
				break;
			}
		}
	}
	else
	{
		errno = ENOMEM;
		ptr = NULL;
	}
#else
	ptr = realloc(oldptr, newsize);

	if (ptr)
	{
		k_atominc_ptr(&k_impl_heap.cnt_alloc);
		k_atominc_ptr(&k_impl_heap.cnt_free);
	}
#endif

	return ptr;
}

// 힙 해제.
KAPI void k_heapfree(kpointer ptr)
{
#if _SB_WINDOWS_
	_k_heap_win32_free(ptr);
#else
	free(ptr);
#endif
	k_atominc_ptr(&k_impl_heap.cnt_free);
}

// 힙 메모리가 맞나 검사.
KAPI kcham k_heapvalid(kpointer ptr)
{
	if (!ptr)
		return FALSE;

#if _SB_WINDOWS_
	if (!_k_heap_win32_valid(ptr))
		return FALSE;
#endif

	return TRUE;
}

// 힙 개수 증가.
static ksize_t k_heapinc(ksize_t size)
{
	volatile ksize_t ret = k_atomget_ptr(&k_impl_heap.size) + size;

	k_atomset_ptr(&k_impl_heap.size, ret);

	if (k_atomget_ptr(&k_impl_heap.max) < ret)
		k_atomset_ptr(&k_impl_heap.max, ret);

	return ret;
}

// 힙 개수 감소.
static ksize_t k_heapdec(ksize_t size)
{
	volatile ksize_t ret = k_atomget_ptr(&k_impl_heap.size) - size;

	k_atomset_ptr(&k_impl_heap.size, ret);

	return ret;
}


//////////////////////////////////////////////////////////////////////////
// 프루프 메모리

#define USE_MPF_LOCK	1

static struct kImplMemProof
{
	kMemBlock*			frst;
	kMemBlock*			last;
	ksize_t				index;
	ksize_t				count;

#if USE_MPF_LOCK
	kLock				lock;
#else
	STUB_MUTEX			lock;
#endif

	kint				dotype;     // debug output type [0=normal, 1=hex, 2=text]
} k_impl_mpf =
{
	NULL,
	NULL,
	0,
	0,

	KLOCK_INIT,

	0
};

#if USE_MPF_LOCK
#define MPF_LOCK			K_LOCK(k_impl_mpf.lock)
#define MPF_UNLOCK			K_UNLOCK(k_impl_mpf.lock)
#else
#define MPF_LOCK			stub_mutex_enter(&k_impl_mpf.lock)
#define MPF_UNLOCK			stub_mutex_leave(&k_impl_mpf.lock)
#endif

void _k_memproof_init(void)
{
#if !USE_MPF_LOCK
	stub_mutex_init(&k_impl_mpf.lock, 4000);
#endif
}

// 제거
void _k_memproof_disp(void)
{
	if (k_impl_mpf.count == 0 && k_impl_mpf.frst == NULL && k_impl_mpf.last == NULL)
		k_traces(KLV_RESULT, "k.mpf", "safe.");
	else
	{
		kMemBlock* node;
		kMemBlock* next;
		ksize_t sums, sumb;
		double size;
		int usage;
#if _SB_WINDOWS_
		int isdbg = k_candebug();
		ksize_t idx = 1;
#endif
		char dump[64];

		k_tracef(KLV_CRITICAL, "k.mpf", "found %d proof memories.", k_impl_mpf.count);

		for (sumb = sums = 0, node = k_impl_mpf.frst; node; node = next)
		{
			if (node->line)
			{
#if !_SB_WINDOWS_
				k_tracef(KLV_INFO, "k.mpf", "size=%Lu(%Lu), \"%s : %d\"",
					node->size, node->block, node->filename, node->line);
#else
				if (isdbg)
				{
					k_outputf("%s(%d) : {%Lu} 0x%p", node->filename, node->line, idx++, k_memptr(node));

					if (k_impl_mpf.dotype == 1)
					{
						// 헥사
						k_outputf(" size=%Lu(%Lu) [%s]\n", node->size, node->block,
							k_memdmp_hex(k_memptr(node), node->size, dump, 42));
					}
					else if (k_impl_mpf.dotype == 2)
					{
						// 텍스트
						k_outputf(" size=%Lu(%Lu) <%s>\n", node->size, node->block,
							k_memdmp(k_memptr(node), node->size, dump, 42));
					}
					else
					{
						// 자동
						if (node->size > 16)
						{
							k_outputf(" size=%Lu(%Lu) <%s>\n", node->size, node->block,
								k_memdmp(k_memptr(node), node->size, dump, 42));
						}
						else
						{
							k_outputf(" size=%Lu(%Lu) [%s]\n", node->size, node->block,
								k_memdmp_hex(k_memptr(node), node->size, dump, 42));
						}
					}
				}
				else
				{
					k_tracef(KLV_INFO, "k.mpf", "size=%Lu(%Lu), \"%s : %d\", 0x%p",
						node->size, node->block, node->filename, node->line, k_memptr(node));
				}
#endif
			}
			else
			{
				if (node->size > 16)
				{
					k_tracef(KLV_INFO, "k.mpf", "size=%Lu(%Lu), <%s>",
						node->size, node->block, k_memdmp(k_memptr(node), node->size, dump, 42));
				}
				else
				{
					k_tracef(KLV_INFO, "k.mpf", "size=%Lu(%Lu), [%s]",
						node->size, node->block, k_memdmp_hex(k_memptr(node), node->size, dump, 42));
				}
			}

			next = node->next;
			sums += node->size;
			sumb += node->block;
			k_heapfree(node);
		}

		k_heapdec(sumb);

		usage = k_memudch(sums, &size);

		if (usage == ' ')
			k_tracef(KLV_INFO, "k.mpf", "user size : %Lu bytes", sums);
		else
			k_tracef(KLV_INFO, "k.mpf", "user size : %f %cbytes", size, usage);

		usage = k_memudch(sumb, &size);

		if (usage == ' ')
			k_tracef(KLV_INFO, "k.mpf", "block size : %Lu bytes", sumb);
		else
			k_tracef(KLV_INFO, "k.mpf", "block size : %f %cbytes", size, usage);
	}

#if !USE_MPF_LOCK
	stub_mutex_disp(&k_impl_mpf.lock);
#endif
}

// 블럭 추가
static void _k_memproof_add(kMemBlock* node, ksize_t block)
{
	MPF_LOCK;

	if (k_impl_mpf.frst)
		k_impl_mpf.frst->prev = node;
	else
		k_impl_mpf.last = node;

	node->next = k_impl_mpf.frst;
	node->prev = NULL;

	k_impl_mpf.frst = node;

	k_impl_mpf.count++;
	k_impl_mpf.index++;
	k_heapinc(block);

	MPF_UNLOCK;
}

// 블럭 제거
static void _k_memproof_remove(kMemBlock* node, ksize_t block)
{
	MPF_LOCK;

	if (node->next)
		node->next->prev = node->prev;
	else
	{
		k_assert(k_impl_mpf.last == node, NULL);
		k_impl_mpf.last = node->prev;
	}

	if (node->prev)
		node->prev->next = node->next;
	else
	{
		k_assert(k_impl_mpf.frst == node, NULL);
		k_impl_mpf.frst = node->next;
	}

	k_impl_mpf.count--;
	k_heapdec(block);

	MPF_UNLOCK;
}

//////////////////////////////////////////////////////////////////////////
// 프루프

/**
 * 프루프 메모리 할당 크기.
 * @return	현재 최대 할당 크기.
 */
KAPI ksize_t k_mpfsize(void)
{
	return k_heapsize();
}

/**
 * 프루프 메모리 할당 개수.
 * @return	현재 최대 할당 개수.
 */
KAPI ksize_t k_mpfcount(void)
{
	return k_impl_mpf.count;
}

/**
 * 프루프 할당.
 * @param	size	할당할 메모리 크기.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 kMemBlock*.
 */
KAPI kMemBlock* k_mpfalloc(ksize_t size)
{
	kMemBlock* node;
	ksize_t block;

	k_assert(size > 0, NULL);

	block = k_memcalcblock(size);
	node = (kMemBlock*)k_heapalloc(block);

	if (!node)
	{
		k_tracef(KLV_FATAL, "k.mpf", "cannot allocate memory! (block size: %Lu)", block);
		return NULL;
	}

	_k_memproof_add(node, block);

	node->type = 1;
	node->index = k_impl_mpf.index;
	node->req = 0;

	node->size = size;
	node->block = block;

#if _SB_DEBUG_
#if !_SB_64_
	node->align[0] = node->align[1] = node->align[2] = 0;
#endif
	node->gap = KMEM_DBG;
#endif

	return node;
}

/**
 * 프루프 할당.
 * @param	size	할당할 메모리 크기.
 * @return	문제가 있거나 실패하면 널값을 반환, 성공할 때 반환값은 kMemBlock*.
 */
KAPI kMemBlock* k_mpfallocf(ksize_t size)
{
	kMemBlock* node;
	ksize_t block;

	k_assert(size > 0, NULL);

	block = k_memcalcblock(size);
	node = (kMemBlock*)k_heapallocf(block);

	if (!node)
	{
		k_tracef(KLV_FATAL, "k.mpf", "cannot allocate memory! (block size: %Lu)", block);
		return NULL;
	}

	_k_memproof_add(node, block);

	node->type = 1;
	node->index = k_impl_mpf.index;
	node->req = 0;

	node->size = size;
	node->block = block;

#if _SB_DEBUG_
#if !_SB_64_
	node->align[0] = node->align[1] = node->align[2] = 0;
#endif
	node->gap = KMEM_DBG;
#endif

	return node;
}

/**
 * 프루프 해제.
 * @param [입력,반환]	node	해제할 노드로 널값이면 안됨.
 */
KAPI void k_mpffree(kMemBlock* node)
{
	k_assert(node != NULL, NULL);

#if _SB_DEBUG_ || TRUE
	if (!k_heapvalid(node))
	{
		char sz[260];
		void* ptr=k_memptr(node);
		k_memdmp(ptr, 19, sz, 260 - 1);
		k_outputf("MEMINFO [%p] %s\n", ptr, sz);
		k_outputf("MEMINFO %s:%d [%u]\n", node->filename, node->line, (kuint)node->size);
		k_halt("k.heap", "TRY TO FREE THE FREED MEMORY?");
	}
#endif

#if _SB_DEBUG_
#if !_SB_64_
	node->align[0] = 0xAE18BAAD;
	node->align[1] = 0x1200BAAD;
	node->align[2] = 0x8282BAAD;
#endif
	node->gap = KMEM_END;
#endif

	_k_memproof_remove(node, node->block);
	k_heapfree(node);
}

/**
 * 프루프 디버그 출력 타입.
 * @param	type	출력할 타입 [0=기본, 1=16진수, 2=문자열].
 */
KAPI void k_mpfdotn(kint type)
{
	k_impl_mpf.dotype = type;
}

/**
 * 프루프 메모리 할당 내용을 디버그 콘솔로 출력한다.
 * @param	byhex	16진수로 덤프하려면 참으로 한다.
 */
KAPI void k_mpfprint(kcham byhex)
{
	kMemBlock* node;
	ksize_t ss, sb, cnt;   // ss=크기합, sb=블럭합
	double size;
	int usage;
	char dmp[64];

	k_outputf(" %-04s | %-8s | %-8s | %-s\n", "no", "size", "block", "desc");

	MPF_LOCK;

	for (sb = ss = 0, cnt = 1, node = k_impl_mpf.frst; node; node = node->next, cnt++)
	{
		if (node->line)
		{
			k_outputf(" %-4d | %-8Lu | % -8Lu | \"%s:%d\"\n",
				cnt, node->size, node->block, node->filename, node->line);
		}
		else
		{
			if (byhex)
			{
				k_outputf(" %-4d | %-8Lu | % -8Lu | <%s>\n",
					cnt, node->size, node->block, k_memdmp_hex(k_memptr(node), node->size, dmp, 42));
			}
			else
			{
				k_outputf(" %-4d | %-8Lu | % -8Lu | [%s]\n",
					cnt, node->size, node->block, k_memdmp(k_memptr(node), node->size, dmp, 42));
			}
		}

		ss += node->size;
		sb += node->block;
	}

	MPF_UNLOCK;

	usage = k_memudch(ss, &size);
	k_outputf("--- user size : %.3g%cbytes ---\n", size, usage);

	usage = k_memudch(sb, &size);
	k_outputf("--- block size : %.3g%cbytes ---\n", size, usage);

	k_outputf("--- total proofs : %Lu ---\n", cnt - 1);
}

